﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gunningine
{
    using OpenTK;
    using OpenTK.Input;

    /// <summary>
    /// This wraps OpenTK input functionality within a facade.
    /// </summary>
    public class InputManager : IDisposable
    {
        /// <summary>
        /// Standard key codes though we obviously don't need all 256.
        /// </summary>
        public const int MAX_KEYS = 256;

        /// <summary>
        /// Only use left, middle, right.  We don't care about the extra
        /// buttons.
        /// </summary>
        public const int MAX_BUTTONS = 3;

        /// <summary>
        /// Store current and previous state of keys.
        /// </summary>
        private bool[] prevKeysDown = new bool[MAX_KEYS];
        private bool[] curKeysDown = new bool[MAX_KEYS];

        /// <summary>
        /// Store current and previous state of mouse buttons.
        /// </summary>
        private bool[] prevButtonsDown = new bool[MAX_BUTTONS];
        private bool[] curButtonsDown = new bool[MAX_BUTTONS];

        /// <summary>
        /// Store current and previous mouse position.
        /// </summary>
        private int prevMouseX, prevMouseY;
        private int curMouseX, curMouseY;

        /// <summary>
        /// Store current previous mouse wheel.
        /// </summary>
        private float prevMouseWheel;
        private float curMouseWheel;

        /// <summary>
        /// Mouse X position.
        /// </summary>
        public int MouseX
        {
            get { return this.curMouseX; }
        }

        /// <summary>
        /// Mouse Y position.
        /// </summary>
        public int MouseY
        {
            get { return this.curMouseY; }
        }

        /// <summary>
        /// Mouse X delta (how much it has moved since last frame).
        /// </summary>
        public int MouseXDelta
        {
            get { return this.curMouseX - this.prevMouseX; }
        }

        /// <summary>
        /// Mouse Y delta (how much it has moved since last frame).
        /// </summary>
        public int MouseYDelta
        {
            get { return this.curMouseY - this.prevMouseY; }
        }

        /// <summary>
        /// Mousewheel position.
        /// </summary>
        public float MouseWheel
        {
            get { return this.curMouseWheel; }
        }

        /// <summary>
        /// Mousewheel delta (how much it has moved since last frame).
        /// </summary>
        public float MouseWheelDelta
        {
            get { return this.curMouseWheel - this.prevMouseWheel; }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public InputManager()
        {
            for (int i = 0; i < MAX_KEYS; ++i)
            {
                this.prevKeysDown[i] = false;
                this.curKeysDown[i] = false;
            }

            for (int i = 0; i < MAX_BUTTONS; ++i)
            {
                this.prevButtonsDown[i] = false;
                this.curButtonsDown[i] = false;
            }

            this.prevMouseX = this.prevMouseY = 0;
            this.curMouseX = this.curMouseY = 0;
            this.curMouseWheel = this.prevMouseWheel = 0;
        }

        /// <summary>
        /// Callback for when a key is pressed.
        /// </summary>
        /// <param name="snder">calling object.</param>
        /// <param name="e">event arguments.</param>
        public void HandleKeyPressed(object sender, KeyboardKeyEventArgs e)
        {
            this.curKeysDown[e.ScanCode] = true;
        }

        /// <summary>
        /// Callback for when a key is released.
        /// </summary>
        /// <param name="snder">calling object.</param>
        /// <param name="e">event arguments.</param>
        public void HandleKeyReleased(object sender, KeyboardKeyEventArgs e)
        {
            this.curKeysDown[e.ScanCode] = false;
        }

        /// <summary>
        /// Callback for when a button is pressed.
        /// </summary>
        /// <param name="sender">calling object.</param>
        /// <param name="e">event arguments.</param>
        public void HandleButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.curButtonsDown[(int)e.Button] = true;
        }

        /// <summary>
        /// Callback for when a button is released.
        /// </summary>
        /// <param name="sender">calling object.</param>
        /// <param name="e">event arguments.</param>
        public void HandleButtonUp(object sender, MouseButtonEventArgs e)
        {
            this.curButtonsDown[(int)e.Button] = false;
        }

        /// <summary>
        /// Callback for when mouse is moved.
        /// </summary>
        /// <param name="sender">calling object.</param>
        /// <param name="e">event arguments.</param>
        public void HandleMouseMove(object sender, MouseMoveEventArgs e)
        {
            this.curMouseX = e.X;
            this.curMouseY = e.Y;
        }

        /// <summary>
        /// Callback for when mouse wheel is spun.
        /// </summary>
        /// <param name="sender">calling object.</param>
        /// <param name="e">event arguments.</param>
        public void HandleMouseWheel(object sender, MouseWheelEventArgs e)
        {
            this.curMouseWheel = e.ValuePrecise;
        }

        /// <summary>
        /// Checks whether a key has been pressed since the previous update.
        /// </summary>
        /// <param name="scanCode">key scan code (see KeyCode enum)</param>
        /// <returns>whether or not the key was pressed.</returns>
        public bool IsKeyPressed(KeyCode scanCode)
        {
            return this.curKeysDown[(int)scanCode] && !this.prevKeysDown[(int)scanCode];
        }

        /// <summary>
        /// Checks whether a key has been released since the previous update.
        /// </summary>
        /// <param name="scanCode">key scan code (see KeyCode enum)</param>
        /// <returns>whether or not the key was released.</returns>
        public bool IsKeyReleased(KeyCode scanCode)
        {
            return !this.curKeysDown[(int)scanCode] && this.prevKeysDown[(int)scanCode];
        }

        /// <summary>
        /// Checks whether a key is down.
        /// </summary>
        /// <param name="scanCode">key scan code (see KeyCode enum)</param>
        /// <returns>whether or not the key is down.</returns>
        public bool IsKeyDown(KeyCode scanCode)
        {
            return this.curKeysDown[(int)scanCode];
        }

        /// <summary>
        /// Checks whether a key is up.
        /// </summary>
        /// <param name="scanCode">key scan code (see KeyCode enum)</param>
        /// <returns>whether or not the key is up.</returns>
        public bool IsKeyUp(KeyCode scanCode)
        {
            return !this.curKeysDown[(int)scanCode];
        }

        /// <summary>
        /// Checks whether a button has been pressed since the previous update.
        /// </summary>
        /// <param name="button">button (see ButtonCode enum)</param>
        /// <returns>whether or not the button was pressed.</returns>
        public bool IsButtonPressed(ButtonCode button)
        {
            return this.curButtonsDown[(int)button] && !this.prevButtonsDown[(int)button];
        }

        /// <summary>
        /// Checks whether a button has been released since the previous update.
        /// </summary>
        /// <param name="button">button (see Button enum)</param>
        /// <returns>whether or not the button was released.</returns>
        public bool IsButtonReleased(ButtonCode button)
        {
            return !this.curButtonsDown[(int)button] && this.prevButtonsDown[(int)button];
        }

        /// <summary>
        /// Checks whether a button is down.
        /// </summary>
        /// <param name="button">button (see ButtonCode enum)</param>
        /// <returns>whether or not the button is down.</returns>
        public bool IsButtonDown(ButtonCode button)
        {
            return this.curButtonsDown[(int)button];
        }

        /// <summary>
        /// Checks whether a key is up.
        /// </summary>
        /// <param name="button">button (see ButtonCode enum)</param>
        /// <returns>whether or not the button is up.</returns>
        public bool IsButtonUp(ButtonCode button)
        {
            return !this.curButtonsDown[(int)button];
        }

        /// <summary>
        /// Force the mouse to a new position.
        /// </summary>
        /// <param name="x">new x.</param>
        /// <param name="y">new y.</param>
        public void TeleportMouse(int x, int y)
        {
            this.prevMouseX = this.curMouseX = x;
            this.prevMouseY = this.curMouseY = y;
        }
            
        /// <summary>
        /// Update the input manager each frame.  We could update the previous
        /// values in the handlers, but this would mean that events will get 
        /// missed if there is more than one between updates.
        /// </summary>
        public void PostUpdate()
        {
            // Update keys
            for (int i = 0; i < MAX_KEYS; ++i)
                this.prevKeysDown[i] = this.curKeysDown[i];

            // Update mouse
            this.prevMouseX = this.curMouseX;
            this.prevMouseY = this.curMouseY;

            // Update buttons
            for (int i = 0; i < MAX_BUTTONS; ++i)
                this.prevButtonsDown[i] = this.curButtonsDown[i];

            // Update mouse wheel
            this.prevMouseWheel = this.curMouseWheel;
        }

        #region IDisposable implementation

        bool _disposed = false;

        public void Dispose()
        {
            if (!_disposed)
            {
                this._disposed = true;
            }
        }

        #endregion
    }
}